<?php

namespace Xin\LaravelFortify\Foundation\Exceptions;

use Fruitcake\Cors\CorsService;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Redirect;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Throwable;

abstract class Handler extends ExceptionHandler
{
	/**
	 * The list of the inputs that are never flashed to the session on validation exceptions.
	 *
	 * @var array<int, string>
	 */
	protected $dontFlash = [
		'current_password',
		'password',
		'password_confirmation',
	];

	/**
	 * Prepare a JSON response for the given exception.
	 *
	 * @param Request $request
	 * @param Throwable $e
	 * @return JsonResponse
	 */
	protected function prepareJsonResponse($request, Throwable $e)
	{
		return new JsonResponse(
			$this->convertExceptionToArray($e),
			$this->isHttpException($e) ? $e->getStatusCode() : 500,
			$this->isHttpException($e) ? $e->getHeaders() : [],
			JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
		);
	}

	/**
	 * Convert the given exception to an array.
	 *
	 * @param \Throwable $e
	 * @return array
	 */
	protected function convertExceptionToArray(Throwable $e): array
	{
		$code = $this->isHttpException($e) ? $e->getStatusCode() : 0;
		return config('app.debug') ? [
			'code' => $code,
			'msg' => $e->getMessage(),
			'exception' => get_class($e),
			'file' => $e->getFile(),
			'line' => $e->getLine(),
			'trace' => collect($e->getTrace())->map(fn($trace) => Arr::except($trace, ['args']))->all(),
		] : [
			'code' => $code,
			'msg' => $this->isHttpException($e) ? $e->getMessage() : 'Server Error',
		];
	}

	/**
	 * Prepare a response for the given exception.
	 *
	 * @param Request $request
	 * @param \Throwable $e
	 * @return SymfonyResponse
	 */
	protected function prepareResponse($request, Throwable $e)
	{
		if ($this->isHttpException($e) && $e->getStatusCode() == 419) {
			return Redirect::refresh();
		}

		return parent::prepareResponse($request, $e);
	}

	/**
	 * Convert an authentication exception into a response.
	 *
	 * @param Request $request
	 * @param AuthenticationException $exception
	 * @return SymfonyResponse
	 */
	protected function unauthenticated($request, AuthenticationException $exception)
	{
		return $this->shouldReturnJson($request, $exception)
			? $this->unauthenticatedJson($exception)
			: redirect()->guest($exception->redirectTo() ?? route('login'));
	}

	/**
	 * Determine if the exception handler response should be JSON.
	 *
	 * @param Request $request
	 * @param \Throwable $e
	 * @return bool
	 */
	protected function shouldReturnJson($request, Throwable $e): bool
	{
		return $request->expectsJson() || $request->is([
				'api/*',
				'admin/*',
				'adminapi/*',
				'notify/*',
			]);
	}

	/**
	 * Convert an authentication exception into a JSON response.
	 * @param AuthenticationException $exception
	 * @return mixed
	 */
	abstract protected function unauthenticatedJson(AuthenticationException $exception);

	/**
	 * 给响应实例添加 Cors Header 支持
	 * @param SymfonyResponse $response
	 * @return mixed|SymfonyResponse|Response
	 */
	protected function responseAddCorsHeaders(SymfonyResponse $response)
	{
		/** @var CorsService $cors */
		$cors = $this->container->make(CorsService::class);
		$cors->setOptions($this->container['config']->get('cors', []));
		return $cors->addActualRequestHeaders($response, app('request'));
	}

	/**
	 * @inerhitDoc
	 */
	protected function context()
	{
		if (app()->runningInConsole()) {
			return [];
		}

		return parent::context();
	}
}
